Platform-Specific Styling and Reusable Design Systems in React Native
Course: React Native Training
Topic: Platform-Specific Styling and Reusable Design Systems
Course Link: React Native Training Course
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction to Platform-Specific Styling
React Native allows developers to build mobile applications for platforms such as Android and iOS using a shared JavaScript or TypeScript codebase. Although most application logic and interface components can be shared, certain visual details may need to change to match each platform's conventions and behavior.
Platform-specific styling means applying different styles, layouts, or visual treatments depending on the operating system or device on which the application runs. For example, a developer may use a different shadow treatment on iOS and Android, adjust spacing around a header, or provide platform-specific styling for a native control.
1.1 Why Platform-Specific Styling Matters
- Native appearance: Helps the application feel familiar to users on each operating system.
- Consistent usability: Accounts for differences in platform conventions, system controls, and navigation.
- Visual compatibility: Helps address differences in shadows, fonts, spacing, and component rendering.
- Responsive presentation: Allows the interface to adapt to device characteristics when necessary.
- Maintainability: Separates platform-specific differences from shared application logic.
1.2 Shared Styling vs Platform-Specific Styling
| Aspect |
Shared Styling |
Platform-Specific Styling |
| Purpose |
Provides a consistent design across platforms. |
Handles differences between platforms. |
| Examples |
Common colors, typography, spacing, and card layouts. |
Different shadows, platform-specific controls, or header adjustments. |
| Code organization |
Shared component and style files. |
Platform.select(), Platform.OS, or platform-specific files. |
| Maintenance |
Usually easier because the same implementation is reused. |
Requires additional testing and maintenance for each variant. |
| Recommended approach |
Use whenever the design and behavior can remain the same. |
Use only when a real platform difference justifies it. |
1.3 Examples of Platform Differences
- Shadow and elevation effects may need different implementations.
- Native buttons, pickers, and other controls may follow different operating-system conventions.
- Safe areas and status bars can affect how screen content is positioned.
- Fonts and text rendering can differ slightly between devices.
- Keyboard behavior and system navigation may affect screen layout.
The goal is not to create two completely separate applications. It is to share as much code as possible while handling genuine platform differences carefully.
2. Understanding the Platform Module
React Native provides the Platform module to detect the current platform and select platform-specific values. It is useful when a small part of a component or its styling needs to behave differently on Android and iOS.
2.1 Importing Platform
import { Platform, StyleSheet, View, Text } from 'react-native';
The Platform module can be used in conditional statements, style definitions, and other platform-dependent logic.
2.2 Using Platform.OS
The Platform.OS property identifies the operating system running the application. In standard React Native mobile applications, common values include 'ios' and 'android'.
import React from 'react';
import { View, Text, Platform, StyleSheet } from 'react-native';
export default function PlatformExample() {
const platformName =
Platform.OS === 'ios' ? 'iOS' : 'Android or another platform';
return (
<View style={styles.container}>
<Text style={styles.text}>
Current platform: {platformName}
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
text: {
fontSize: 18,
color: '#1E293B',
},
});
This example checks whether the current platform is iOS. The fallback label is intentionally broad because React Native projects may target platforms other than Android and iOS.
2.3 Conditional Styling with Platform.OS
You can use a conditional expression inside a style definition to change a value based on the platform.
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
padding: Platform.OS === 'ios' ? 20 : 16,
borderRadius: Platform.OS === 'ios' ? 14 : 10,
backgroundColor: '#FFFFFF',
},
});
In this example, the container uses different padding and corner radii on iOS and other platforms. Use this technique sparingly: if the shared design looks good on both platforms, a single value is easier to maintain.
2.4 When to Use Platform.OS
- When a single style property needs a different value.
- When a small piece of UI behavior depends on the operating system.
- When a native feature is available only on a particular platform.
- When a platform-specific adjustment is simpler than maintaining separate files.
3. Using Platform.select()
The Platform.select() method selects a value from an object based on the current platform. It is particularly useful for defining platform-specific styles while keeping common styles together.
3.1 Basic Syntax
Platform.select({
ios: {
// iOS-specific values
},
android: {
// Android-specific values
},
default: {
// Fallback values for other platforms
},
});
React Native chooses the matching platform entry. If a specific platform entry is unavailable, the selection can fall back to native and then default, when those keys are supplied.
3.2 Platform-Specific Container Styles
import React from 'react';
import { View, Text, Platform, StyleSheet } from 'react-native';
export default function PlatformCard() {
return (
<View style={styles.card}>
<Text style={styles.title}>Platform-Aware Card</Text>
<Text style={styles.description}>
This card shares its main design across platforms.
</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
padding: 18,
borderRadius: 12,
backgroundColor: '#FFFFFF',
borderWidth: 1,
borderColor: '#E2E8F0',
...Platform.select({
ios: {
shadowColor: '#000000',
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.12,
shadowRadius: 5,
},
android: {
elevation: 4,
},
default: {},
}),
},
title: {
fontSize: 18,
fontWeight: '700',
color: '#0F172A',
},
description: {
marginTop: 8,
fontSize: 14,
lineHeight: 21,
color: '#475569',
},
});
The card shares its padding, border, radius, and colors. Its shadow treatment changes by platform because Android and iOS use different mechanisms for elevation and shadows. The final appearance should still be tested on real devices or simulators.
3.3 Selecting Individual Values
Platform.select() can return a single value rather than a complete style object.
import { Platform } from 'react-native';
const headerHeight = Platform.select({
ios: 56,
android: 60,
default: 56,
});
This is useful for small layout adjustments. However, fixed header heights should be checked against safe areas, accessibility text scaling, and the actual content that the header must display.
3.4 Choosing Platform-Specific Components
The selection method can also select component implementations or functions that return components. For more complex components, separate platform-specific files are often clearer.
const PlatformComponent = Platform.select({
ios: () => require('./CustomControl.ios').default,
android: () => require('./CustomControl.android').default,
default: () => require('./CustomControl').default,
});
const CustomControl = PlatformComponent();
This pattern illustrates the concept, but actual component exports and module formats must match the project's configuration. For larger components, direct imports using platform-specific file extensions are usually easier to understand.
4. Platform-Specific File Extensions
When an entire component needs different implementations for Android and iOS, React Native supports platform-specific file extensions. Metro can resolve the appropriate file when a component is imported without the platform suffix.
4.1 Common File Naming Conventions
| File Extension |
Typical Purpose |
Example |
.ios.js or .ios.tsx |
iOS-specific component implementation. |
Header.ios.tsx |
.android.js or .android.tsx |
Android-specific component implementation. |
Header.android.tsx |
.native.js or .native.tsx |
Code shared by React Native native platforms, where the project tooling supports this convention. |
Theme.native.ts |
| Regular extension |
Shared or fallback implementation. |
Header.tsx |
4.2 Example Project Structure
src/
components/
AppHeader.ios.tsx
AppHeader.android.tsx
AppHeader.tsx
screens/
HomeScreen.tsx
theme/
colors.ts
spacing.ts
typography.ts
App.tsx
4.3 Importing a Platform-Specific Component
import AppHeader from './components/AppHeader';
With the appropriate files in place, Metro resolves the platform-specific implementation for the current build. The import statement remains the same, which keeps screen code simple.
4.4 Example: iOS Header
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default function AppHeader() {
return (
<View style={styles.header}>
<Text style={styles.title}>My Application</Text>
</View>
);
}
const styles = StyleSheet.create({
header: {
minHeight: 56,
justifyContent: 'center',
paddingHorizontal: 20,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderBottomColor: '#E2E8F0',
},
title: {
fontSize: 20,
fontWeight: '600',
color: '#0F172A',
},
});
Save the appropriate implementation as AppHeader.ios.tsx. An Android implementation can use different spacing, elevation, or visual details if the design requires it.
4.5 When to Use Separate Files
- When the component's structure differs significantly between platforms.
- When native controls or APIs differ substantially.
- When a component becomes difficult to read because of many platform conditions.
- When each platform needs a different implementation but the parent screen should remain shared.
Avoid creating duplicate files for components that differ only by a small color or spacing value. In those cases, shared styles with Platform.select() are generally more maintainable.
5. Platform-Specific Styling Patterns
5.1 Shadows and Elevation
Shadow effects are a common reason to introduce platform-specific styles. Android commonly uses the elevation property for native elevation effects, while iOS shadow styling commonly uses properties such as shadowColor, shadowOffset, shadowOpacity, and shadowRadius.
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
card: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 16,
...Platform.select({
ios: {
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 3,
},
default: {},
}),
},
});
Keep shadows subtle and verify their appearance on the target platforms. Shadow properties do not produce identical results across operating systems.
5.2 Platform-Specific Spacing
Spacing may need adjustment when a header, native control, or system interface occupies a different amount of space on different platforms.
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
screenContent: {
paddingHorizontal: 16,
paddingTop: Platform.select({
ios: 18,
android: 14,
default: 16,
}),
},
});
Do not use arbitrary platform-specific padding to compensate for a layout problem that should instead be solved with flexbox or safe-area handling.
5.3 Platform-Specific Typography
System fonts and text rendering can vary across platforms. A shared typography system can use common font sizes, weights, and line heights while selectively using platform-appropriate font families if required.
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
heading: {
fontSize: 24,
fontWeight: '700',
lineHeight: 31,
fontFamily: Platform.select({
ios: 'System',
android: 'sans-serif',
default: undefined,
}),
},
});
Font family names and available weights can vary by platform and project configuration. Verify supported fonts before relying on a specific family. For many applications, using the system default font with shared size and weight rules is sufficient.
5.4 Platform-Specific Border and Radius Values
const styles = StyleSheet.create({
button: {
paddingVertical: 12,
paddingHorizontal: 18,
borderWidth: 1,
borderColor: '#CBD5E1',
borderRadius: Platform.select({
ios: 12,
android: 8,
default: 10,
}),
},
});
This technique allows a small visual difference while keeping the button's content and general appearance consistent.
5.5 Safe Areas and Screen Edges
Safe areas are regions where important content should avoid overlapping system elements, such as a notch, status bar, or home indicator. Safe-area requirements depend on the device, orientation, navigation setup, and operating system.
For projects that need explicit safe-area handling, developers commonly use the react-native-safe-area-context library. It provides components and hooks for handling safe-area insets.
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import {
SafeAreaProvider,
SafeAreaView,
} from 'react-native-safe-area-context';
function HomeScreen() {
return (
<SafeAreaView style={styles.screen}>
<View style={styles.content}>
<Text style={styles.title}>Welcome</Text>
</View>
</SafeAreaView>
);
}
export default function App() {
return (
<SafeAreaProvider>
<HomeScreen />
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: '#F8FAFC',
},
content: {
flex: 1,
padding: 20,
},
title: {
fontSize: 26,
fontWeight: '700',
color: '#0F172A',
},
});
Install and configure react-native-safe-area-context according to your project's setup before using this example. Navigation libraries may already provide safe-area integration. Avoid applying the same inset twice.
6. Introduction to Reusable Design Systems
A design system is a structured collection of design rules, reusable visual values, components, and usage guidelines. It helps developers create a consistent user interface across screens and platforms.
Instead of writing the same colors, spacing values, font sizes, and border radii repeatedly, a design system defines them in one place. Components then reuse these shared values, reducing duplication and making visual changes easier to maintain.
6.1 What a Design System Contains
- Design tokens: Named values for colors, spacing, typography, border radii, and other common properties.
- Typography rules: Consistent styles for headings, body text, captions, and labels.
- Color palette: Primary, secondary, surface, text, border, success, warning, and error colors.
- Spacing scale: Reusable values for padding, margins, and gaps.
- Reusable components: Buttons, cards, input fields, badges, headers, and other common elements.
- Component variants: Standard, primary, secondary, disabled, loading, and error states.
- Accessibility guidelines: Rules for contrast, touch targets, font scaling, and labels.
- Platform rules: Guidelines for the small number of platform differences that the product needs.
6.2 Benefits of a Reusable Design System
| Benefit |
Explanation |
| Consistency |
Common components and tokens create a unified visual language. |
| Reusability |
Developers can reuse components instead of rebuilding them on each screen. |
| Maintainability |
Changing a shared token can update multiple components consistently. |
| Faster development |
Teams spend less time repeatedly implementing common interface elements. |
| Scalability |
New screens can use existing design rules and component patterns. |
| Accessibility |
Shared components can include standard accessibility behavior and visual requirements. |
| Platform consistency |
Shared styles keep the application coherent while allowing necessary platform adaptations. |
7. Creating Design Tokens
Design tokens are named values that represent reusable design decisions. Instead of repeating a color such as #2563EB throughout an application, developers can define a token such as colors.primary.
7.1 Creating a Color Palette
Create a file such as src/theme/colors.ts.
const colors = {
primary: '#2563EB',
primaryDark: '#1D4ED8',
secondary: '#64748B',
background: '#F8FAFC',
surface: '#FFFFFF',
textPrimary: '#0F172A',
textSecondary: '#475569',
border: '#E2E8F0',
success: '#15803D',
warning: '#B45309',
error: '#DC2626',
};
export default colors;
These named values form the shared color palette. Components can refer to a token rather than defining their own color values.
7.2 Creating Spacing Tokens
Create a file such as src/theme/spacing.ts.
const spacing = {
xs: 4,
sm: 8,
md: 12,
lg: 16,
xl: 24,
xxl: 32,
};
export default spacing;
The spacing scale gives developers a predictable set of values for padding, margins, and gaps. A team can adjust the scale as the design evolves without searching through every component for unrelated numbers.
7.3 Creating Typography Tokens
Create a file such as src/theme/typography.ts.
const typography = {
caption: {
fontSize: 12,
lineHeight: 18,
fontWeight: '400',
},
body: {
fontSize: 16,
lineHeight: 24,
fontWeight: '400',
},
subtitle: {
fontSize: 18,
lineHeight: 26,
fontWeight: '600',
},
heading: {
fontSize: 26,
lineHeight: 34,
fontWeight: '700',
},
};
export default typography;
Typography tokens define common text styles. Choose font sizes and line heights that work with the content and allow layouts to adapt to accessibility text scaling.
7.4 Creating Border Radius Tokens
const radii = {
small: 4,
medium: 8,
large: 12,
extraLarge: 20,
pill: 999,
};
export default radii;
Shared radius tokens help cards, buttons, inputs, and badges follow a consistent shape language.
7.5 Combining Theme Tokens
For a small application, a single theme entry point can export all shared tokens.
// src/theme/index.ts
import colors from './colors';
import spacing from './spacing';
import typography from './typography';
import radii from './radii';
const theme = {
colors,
spacing,
typography,
radii,
};
export default theme;
Use the actual file paths that exist in your project. The example assumes each token file exports its values as the default export.
8. Using Design Tokens in Components
8.1 Creating a Reusable Button
A reusable button should provide consistent padding, typography, colors, corner radii, and interaction behavior. Variants can customize its appearance without duplicating the entire component.
import React from 'react';
import {
Pressable,
Text,
StyleSheet,
Platform,
} from 'react-native';
import theme from '../theme';
export default function PrimaryButton({ title, onPress, disabled = false }) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
accessibilityRole="button"
accessibilityState={{ disabled }}
style={({ pressed }) => [
styles.button,
pressed && !disabled && styles.pressed,
disabled && styles.disabled,
]}
>
<Text style={styles.label}>{title}</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
minHeight: 48,
paddingHorizontal: theme.spacing.xl,
paddingVertical: theme.spacing.md,
alignItems: 'center',
justifyContent: 'center',
borderRadius: theme.radii.medium,
backgroundColor: theme.colors.primary,
...Platform.select({
ios: {
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.12,
shadowRadius: 3,
},
android: {
elevation: 2,
},
default: {},
}),
},
label: {
fontSize: 16,
fontWeight: '700',
color: theme.colors.surface,
},
pressed: {
opacity: 0.82,
},
disabled: {
opacity: 0.5,
},
});
This button uses shared tokens for spacing, radius, and color. Platform-specific shadow styles are isolated in one place, while the button's core design remains consistent.
Note: This example uses JavaScript-style component props. In a TypeScript project, define a props interface or type for title, onPress, and disabled.
8.2 Creating a Reusable Card
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import theme from '../theme';
export default function Card({ title, description, children }) {
return (
<View style={styles.card}>
{title ? <Text style={styles.title}>{title}</Text> : null}
{description ? (
<Text style={styles.description}>{description}</Text>
) : null}
{children}
</View>
);
}
const styles = StyleSheet.create({
card: {
padding: theme.spacing.lg,
backgroundColor: theme.colors.surface,
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.radii.large,
},
title: {
...theme.typography.subtitle,
color: theme.colors.textPrimary,
},
description: {
marginTop: theme.spacing.sm,
...theme.typography.body,
color: theme.colors.textSecondary,
},
});
This card can display different content while maintaining consistent spacing, typography, border, and background styles. It can be reused for profiles, products, course details, and dashboard sections.
8.3 Reusing Components Across Screens
import React from 'react';
import { View, StyleSheet } from 'react-native';
import Card from '../components/Card';
import PrimaryButton from '../components/PrimaryButton';
import theme from '../theme';
export default function CourseScreen() {
return (
<View style={styles.screen}>
<Card
title="React Native Training"
description="Learn components, styling, and cross-platform development."
>
<PrimaryButton
title="Explore Course"
onPress={() => {
console.log('Course selected');
}}
/>
</Card>
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
padding: theme.spacing.lg,
backgroundColor: theme.colors.background,
},
});
The screen composes existing components rather than reimplementing their visual styles. In a production app, replace the demonstration callback with the intended navigation or action.
9. Building Component Variants
A reusable component often needs multiple appearances. A button might be primary, secondary, outlined, or destructive. A card might be standard or highlighted. Variants allow these differences to be expressed through a small API while preserving the shared component structure.
9.1 Button Variants
import React from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';
const buttonVariants = {
primary: {
backgroundColor: '#2563EB',
borderColor: '#2563EB',
},
secondary: {
backgroundColor: '#E2E8F0',
borderColor: '#E2E8F0',
},
outline: {
backgroundColor: 'transparent',
borderColor: '#2563EB',
},
danger: {
backgroundColor: '#DC2626',
borderColor: '#DC2626',
},
};
export default function VariantButton({
title,
variant = 'primary',
onPress,
}) {
const variantStyle = buttonVariants[variant] || buttonVariants.primary;
const isOutline = variant === 'outline';
const isSecondary = variant === 'secondary';
return (
<Pressable
onPress={onPress}
accessibilityRole="button"
style={[styles.button, variantStyle]}
>
<Text
style={[
styles.label,
(isOutline || isSecondary) && styles.darkLabel,
]}
>
{title}
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
minHeight: 46,
paddingHorizontal: 18,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
borderWidth: 1,
},
label: {
fontSize: 16,
fontWeight: '700',
color: '#FFFFFF',
},
darkLabel: {
color: '#1E293B',
},
});
This example illustrates variant-based styling. For a stricter TypeScript implementation, define a union type for supported variants so unsupported values can be caught during development.
9.2 Why Variants Are Useful
- They reduce duplicated component code.
- They keep buttons and cards visually consistent.
- They allow design changes to be made centrally.
- They make component behavior easier to document and test.
- They provide a controlled set of visual choices for application developers.
10. Creating a Reusable Platform-Aware Design System
A platform-aware design system combines shared design tokens and components with a small, deliberate set of platform-specific differences. The main design rules remain consistent, while the implementation adapts where necessary.
10.1 Recommended Folder Structure
src/
components/
Button.tsx
Card.tsx
ScreenContainer.tsx
AppHeader.ios.tsx
AppHeader.android.tsx
theme/
colors.ts
spacing.ts
typography.ts
radii.ts
index.ts
screens/
HomeScreen.tsx
CourseScreen.tsx
utils/
platformStyles.ts
App.tsx
This is an example structure rather than a mandatory convention. Small projects can start with fewer files and grow as shared components and styling needs increase.
10.2 Centralizing Platform Differences
When multiple components use the same platform-specific shadow or spacing rule, a shared helper can reduce duplication.
// src/utils/platformStyles.ts
import { Platform } from 'react-native';
export const cardShadow = Platform.select({
ios: {
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 3,
},
default: {},
});
export const headerPadding = Platform.select({
ios: 20,
android: 16,
default: 16,
});
A component can then import the relevant helper rather than repeat the platform condition. Keep such helpers focused and avoid building a complicated abstraction for differences that occur only once.
10.3 Design System Principles
- Consistency first: Prefer shared values and shared components.
- Platform adaptation second: Add platform differences only when justified by usability, behavior, or design requirements.
- Centralized tokens: Avoid hardcoding common colors and spacing values throughout the codebase.
- Clear component APIs: Use explicit props and a small number of documented variants.
- Accessibility by default: Include appropriate roles, labels, states, contrast, and flexible typography.
- Tested changes: Verify that a token or shared component change does not break unrelated screens.
11. Theme Support and Dark Mode
A design system can support multiple themes, such as light and dark modes. A theme defines the colors and related values used by the interface. Components consume the active theme instead of hardcoding colors independently.
11.1 Defining Light and Dark Themes
export const lightTheme = {
colors: {
background: '#F8FAFC',
surface: '#FFFFFF',
text: '#0F172A',
secondaryText: '#475569',
border: '#E2E8F0',
primary: '#2563EB',
},
};
export const darkTheme = {
colors: {
background: '#0F172A',
surface: '#1E293B',
text: '#F8FAFC',
secondaryText: '#CBD5E1',
border: '#334155',
primary: '#60A5FA',
},
};
The two themes share the same token names but provide different values. This allows components to use the same style logic in both modes.
11.2 Choosing a Theme Based on the System
React Native provides the useColorScheme() hook to read the current appearance preference reported by the device. The returned value can be 'light', 'dark', or null.
import React from 'react';
import { View, Text, StyleSheet, useColorScheme } from 'react-native';
import { lightTheme, darkTheme } from './theme/themes';
export default function ThemedScreen() {
const colorScheme = useColorScheme();
const theme = colorScheme === 'dark' ? darkTheme : lightTheme;
return (
<View
style={[
styles.screen,
{ backgroundColor: theme.colors.background },
]}
>
<Text style={[styles.title, { color: theme.colors.text }]}>
Welcome
</Text>
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
padding: 20,
},
title: {
fontSize: 26,
fontWeight: '700',
},
});
This example follows the system appearance. Applications that offer a manual theme preference should store and apply that preference separately, using the system scheme as a default when appropriate.
11.3 Design System and Theme Responsibilities
| Concept |
Responsibility |
| Design tokens |
Define reusable values such as colors, spacing, and typography. |
| Components |
Provide reusable interface elements with consistent behavior and styling. |
| Theme |
Choose the active set of token values, such as light or dark colors. |
| Platform-specific styles |
Adjust the implementation where operating systems require different treatment. |
| Screen layout |
Arrange components for a particular user flow or feature. |
12. Using Native System Colors
React Native provides PlatformColor for accessing native colors by their system-defined names. This can help a component blend with platform controls or respect native appearance conventions.
import React from 'react';
import {
Platform,
PlatformColor,
View,
Text,
StyleSheet,
} from 'react-native';
export default function NativeColorExample() {
const textColor = Platform.select({
ios: PlatformColor('label'),
android: PlatformColor('?android:attr/textColorPrimary'),
default: '#111827',
});
return (
<View style={styles.container}>
<Text style={[styles.text, { color: textColor }]}>
Platform-aware text color
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 20,
},
text: {
fontSize: 16,
},
});
Important: Native color names must be valid for the target platform and React Native version. Confirm the supported names in the official documentation. Use a shared palette when strict brand colors are required; native colors are most useful when the interface should follow system conventions.
13. Testing Platform-Specific Styles and Design Systems
A platform-aware design system should be tested on each supported platform. A component that looks correct on one device may have different shadows, text wrapping, system insets, or touch behavior on another.
13.1 Visual Testing Checklist
- Verify shared colors, typography, spacing, and radii.
- Check platform-specific shadows and elevation.
- Confirm headers and content respect safe areas.
- Test long titles and larger accessibility text sizes.
- Verify light and dark themes if both are supported.
- Check buttons, input fields, and cards in normal, pressed, disabled, and error states.
- Confirm that images and icons are aligned correctly.
- Test both portrait and landscape layouts when the application supports them.
- Check behavior on different screen sizes and operating-system versions.
13.2 Common Mistakes
| Mistake |
Why It Is a Problem |
Recommended Solution |
| Duplicating every component for each platform |
Creates unnecessary maintenance and inconsistent behavior. |
Share components and separate only the genuinely different parts. |
| Using Platform.OS for every style |
Makes components harder to read and maintain. |
Use shared styles and centralized platform helpers where appropriate. |
| Repeating raw color values |
Creates inconsistent colors and makes global changes difficult. |
Use named design tokens. |
| Hardcoding excessive dimensions |
Can break layouts on smaller devices or with larger text. |
Prefer flexible layouts and content-aware sizing. |
| Ignoring platform conventions |
Controls may feel unfamiliar or behave unexpectedly. |
Review platform guidelines and test with platform users in mind. |
| Creating too many tokens |
Makes the theme complicated without adding useful consistency. |
Begin with a small, meaningful set and expand when needed. |
| Changing tokens without testing components |
May cause visual regressions across multiple screens. |
Test shared components and representative screens after changes. |
| Using colors without checking contrast |
Can reduce readability and accessibility. |
Check contrast and test different appearance settings. |
14. Practical Project: Build a Platform-Aware UI Kit
Project Objective: Create a small reusable UI kit for a React Native application. The kit should share design tokens across screens and apply platform-specific styles only where necessary.
14.1 Project Requirements
- Create a color palette, spacing scale, typography styles, and radius tokens.
- Build a reusable primary button with disabled and pressed states.
- Build a reusable card component with consistent padding and borders.
- Apply platform-specific shadow styles to the card.
- Create a screen container that uses shared theme colors and spacing.
- Add light and dark theme values.
- Test the components on Android and iOS.
14.2 Suggested Folder Structure
src/
theme/
colors.ts
spacing.ts
typography.ts
radii.ts
themes.ts
index.ts
components/
PrimaryButton.tsx
Card.tsx
ScreenContainer.tsx
screens/
DesignSystemDemoScreen.tsx
App.tsx
14.3 Example Demo Screen
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import Card from '../components/Card';
import PrimaryButton from '../components/PrimaryButton';
import theme from '../theme';
export default function DesignSystemDemoScreen() {
return (
<View style={styles.screen}>
<Text style={styles.heading}>Design System Demo</Text>
<Card
title="Reusable Components"
description="Build consistent interfaces with shared design tokens."
>
<View style={styles.buttonContainer}>
<PrimaryButton
title="Get Started"
onPress={() => console.log('Get Started pressed')}
/>
</View>
</Card>
<Card
title="Platform-Aware Styling"
description="Keep the design consistent while adapting to platform differences."
/>
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
padding: theme.spacing.lg,
backgroundColor: theme.colors.background,
},
heading: {
...theme.typography.heading,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.lg,
},
buttonContainer: {
marginTop: theme.spacing.lg,
},
});
Note: This screen assumes the reusable components and theme exports have been created as shown in the earlier examples. Ensure your actual theme includes the referenced token names.
14.4 Expected Outcome
The completed application should show reusable cards and buttons with consistent colors, typography, and spacing. The cards should apply platform-specific shadow treatment where supported, and the overall layout should remain usable on different screen sizes.
14.5 Project Evaluation Checklist
- Are design tokens defined in a central location?
- Are shared components reused across the demo screen?
- Are platform differences limited to the parts that need them?
- Do button variants and disabled states work correctly?
- Does the layout support larger text and different screen dimensions?
- Are the light and dark themes readable?
- Has the UI been tested on Android and iOS?
15. Practice Exercises
- Display the current platform using
Platform.OS.
- Create a card with different shadow treatments for iOS and Android using
Platform.select().
- Create a reusable spacing token file and use it in three components.
- Create a shared typography system for headings, body text, and captions.
- Build a reusable button with primary, secondary, and outline variants.
- Create a platform-specific header using
.ios.tsx and .android.tsx files.
- Create light and dark themes using the same token names.
- Build a card component that can display different titles, descriptions, and child elements.
- Use a shared helper for platform-specific shadows and verify the appearance on both operating systems.
- Test your design system with larger text, long titles, and different screen sizes.
16. Interview Questions and Answers
Q1. What is platform-specific styling in React Native?
Answer: Platform-specific styling means changing selected styles or interface behavior according to the operating system, such as Android or iOS.
Q2. What is Platform.OS?
Answer: It is a property of React Native's Platform module that identifies the current operating system. Standard mobile projects commonly use the values ios and android.
Q3. What is Platform.select()?
Answer: It selects a value from an object based on the current platform. It can be used to choose style objects, individual values, or component implementations.
Q4. When should platform-specific files be used?
Answer: They are useful when a complete component requires a significantly different implementation for each platform. File extensions such as .ios.tsx and .android.tsx allow platform-aware module resolution.
Q5. What is a design system?
Answer: A design system is a collection of reusable design tokens, components, visual rules, and guidelines that helps maintain consistency throughout an application.
Q6. What are design tokens?
Answer: Design tokens are named values for common design decisions, such as colors, spacing, typography, and border radii.
Q7. What is the difference between a design token and a component?
Answer: A design token defines a reusable value, while a component combines styles, structure, and behavior into a reusable interface element.
Q8. Why should styles be shared across screens?
Answer: Shared styles reduce duplication, improve consistency, and make global design changes easier to maintain.
Q9. How can shadows differ between Android and iOS?
Answer: Android commonly uses elevation for native shadow effects, while iOS commonly uses properties such as shadowColor, shadowOffset, shadowOpacity, and shadowRadius.
Q10. What is a component variant?
Answer: A component variant is a predefined visual or behavioral version of a reusable component, such as a primary button, secondary button, or destructive button.
Q11. How can a React Native application support dark mode?
Answer: Define light and dark themes with the same token names and select the appropriate theme based on the system color scheme or a user-selected preference.
Q12. What does useColorScheme() do?
Answer: It is a React Native hook that provides the current system appearance preference, commonly light or dark, or null when unavailable.
Q13. Why should platform-specific styling be limited?
Answer: Excessive platform conditions increase complexity and can make the application harder to maintain. Shared styling should remain the default unless a genuine platform difference requires a separate treatment.
Q14. What is PlatformColor?
Answer: PlatformColor allows React Native components to use native system color resources by their platform-specific names, where supported.
Q15. How do you test a reusable design system?
Answer: Test shared components and representative screens across supported platforms, screen sizes, appearance modes, interaction states, and accessibility settings.
17. Quick Revision Checklist
- Understand the purpose of platform-specific styling.
- Know how to use
Platform.OS for simple conditions.
- Know how to use
Platform.select() for selecting values and styles.
- Understand when to use
.ios.tsx and .android.tsx files.
- Recognize platform differences in shadows, elevation, spacing, and native controls.
- Understand safe-area handling and why duplicate insets should be avoided.
- Know what a design system is and how it improves consistency.
- Create reusable tokens for colors, spacing, typography, and radii.
- Build reusable components with documented variants and states.
- Understand light and dark themes.
- Test accessibility, text scaling, and layouts on supported platforms.
- Keep platform-specific code limited, clear, and well tested.
18. Official Documentation and Learning Resources
19. Conclusion
Platform-specific styling and reusable design systems are important for building consistent, maintainable React Native applications. The Platform module and Platform.select() help handle small platform differences, while platform-specific file extensions support separate implementations when a component genuinely needs them.
Reusable design tokens standardize colors, spacing, typography, and border radii. Reusable components such as buttons and cards apply those tokens consistently across screens. Themes allow the same component system to support different appearance modes, while targeted platform styles help the interface feel appropriate on Android and iOS.
For best results, prioritize shared components, keep platform-specific logic focused, design for accessibility, and test every important interface on the platforms and devices your application supports.
To explore React Native training, visit JustAcademy's React Native Training Course or register for a course demo.